package join

import (
	"container/list"
	"encoding/json"
	"fmt"
	utils "gitee.com/ichub/goconfig/common/base/baseutils/stringutils"
	"gitee.com/ichub/gofactroy/common/ichubbase"
	"gitee.com/ichub/gofactroy/factroy/buildfactroy/code"
	"gitee.com/ichub/gofactroy/factroy/dbfactroy"
	"log"
	"strings"
	"time"
)

const multiPb = "PbMulti"

type JoinQuery struct {
	Tables                            []string //第一个为关联主表
	Table, TableAlias, Pkey, PkeyType string

	Selects string
	//关联主表
	Joins    []string
	GoFields string //go dto GoFields

	Keys  map[string]string
	Alias map[string]string `json:"-"`

	dfcs  []*dbfactroy.DirFileContent       `json:"-"`
	Mapcs map[string](*[]dbfactroy.COLUMNS) `json:"-"`

	Tool *dbfactroy.DbFactroy `json:"-"`
}

func (multiQuery *JoinQuery) AppendDfc(dir, f, c string) {

	multiQuery.dfcs = append(multiQuery.dfcs, &dbfactroy.DirFileContent{dir, f, c})

}

func (multiQuery *JoinQuery) SendEmail() *JoinQuery {

	subj := "生成MultiQuery代码：" + utils.Ip4() + " " + strings.Join(multiQuery.Tables, ",")

	var ca []string

	for _, v := range multiQuery.dfcs {
		ca = append(ca, v.Content)
	}
	c := strings.Join(ca, "<br/")
	c = strings.ReplaceAll(c, "\n", "<br/>")
	c = strings.ReplaceAll(c, "\t", "&nbsp;&nbsp;&nbsp;&nbsp;")
	c = strings.ReplaceAll(c, " ", "&nbsp;")

	multiQuery.Tool.SendEmail(subj, c)
	return multiQuery
}

func (multiQuery *JoinQuery) LogDfc() *JoinQuery {
	log.Println(multiQuery.ToString())

	fmt.Printf("end build multiquery %s...你产生的多表关联代码路径如下:\n", multiQuery.TableAlias)
	log.Println("end build multiquery...你产生的多表关联代码路径如下 ： ")

	for _, v := range multiQuery.dfcs {
		fmt.Println(v.Dir, v.File)
		log.Println(v.Dir, v.File)
	}
	log.Println("build multiquery...你产生的多表关联代码！")
	fmt.Println("build multiquery...你产生的多表关联代码！")

	return multiQuery
}

/*
tables "a,b" employee.id,employee.name
*/
func (multiQuery *JoinQuery) Ini(tool *dbfactroy.DbFactroy, tables string) *JoinQuery {
	multiQuery.Keys = make(map[string]string)
	multiQuery.Alias = make(map[string]string)

	multiQuery.Tables = strings.Split(tables, ",")

	multiQuery.Table = strings.Trim(multiQuery.Tables[0], " ")
	multiQuery.TableAlias = multiQuery.Table
	multiQuery.Tool = tool
	tool.Table = multiQuery.Table

	for _, v := range multiQuery.Tables {
		multiQuery.Alias[v] = v
	}
	return multiQuery.CheckTables()
}

func (multiQuery *JoinQuery) CheckTables() *JoinQuery {
	for _, t := range multiQuery.Tables {
		multiQuery.Tool.Table = t
		b := multiQuery.Tool.CheckTableExist()
		if !b {
			panic(t + "表不存在！")
		}
	}
	return multiQuery
}

func (multiQuery *JoinQuery) SelectFields(selectFields string) *JoinQuery {

	multiQuery.Selects = strings.Trim(selectFields, " ")
	return multiQuery
}

func (multiQuery *JoinQuery) CheckKeys(tableTool *dbfactroy.DbFactroy) {
	if len(multiQuery.Mapcs) == 0 {
		multiQuery.Mapcs = multiQuery.findColumns(tableTool)
	}
	for k, v := range multiQuery.Keys {
		k = strings.Trim(k, " ")
		v = strings.Trim(v, " ")
		c := multiQuery.checkExist(multiQuery.Mapcs, v)
		if c == nil {
			panic(v + "关联key不存在！")
		}
		c = multiQuery.checkExist(multiQuery.Mapcs, k)
		if c == nil {
			panic(k + "关联key不存在！")
		}
	}
}

// db := dbinst.Table("employee").Select("employee.id,employee.name")
// db = db.Joins("JOIN department on department.id = employee.departmentId")
// db = db.Order("id desc ").Limit(5).Find(&ms)
func (multiQuery *JoinQuery) buildJoins(tableTool *dbfactroy.DbFactroy) {
	multiQuery.CheckKeys(tableTool)
	//JOIN department on department.id = employee.departmentId
	for key, value := range multiQuery.Keys {
		t := strings.Split(key, ".")
		if t[0] == multiQuery.Table {
			t = strings.Split(value, ".")
		}
		multiQuery.Joins = append(multiQuery.Joins, fmt.Sprintf("dbc = dbc.Joins(\"JOIN %s on %s = %s\") ", t[0], key, value))
	}

	return
}

/*
指定生成结果转json字符串
*/
func (multiQuery *JoinQuery) String() string {
	s, err := json.Marshal(multiQuery)
	if err != nil {
		fmt.Println(err.Error())
	}
	return string(s)

}

func (multiQuery *JoinQuery) ToString() string {
	s, err := json.MarshalIndent(multiQuery, "", " ")
	if err != nil {
		fmt.Println(err.Error())
	}
	return string(s)

}

func (multiQuery *JoinQuery) checkExist(mapcs map[string](*[]dbfactroy.COLUMNS), fields string) *dbfactroy.COLUMNS {
	fields = strings.Trim(fields, " ")
	table_fields := strings.Split(fields, ".")
	cs := mapcs[strings.Trim(table_fields[0], " ")]
	if cs == nil {
		return nil
	}
	for _, v := range *cs {
		if v.COLUMN_NAME == table_fields[1] {
			return &v
		}
	}
	return nil
}
func (multiQuery *JoinQuery) MakeModelProtoBody(columns *[]dbfactroy.COLUMNS) *list.List {

	lst := list.New()
	i := 5
	for _, v := range *columns {
		// fmt.Println(v.String())
		dt := dbfactroy.InstContext.FindProtoType(v.DATA_TYPE)
		if dt == "" {
			dt = "string"
		}
		modelName := utils.Case2Camel(v.TableName)
		lst.PushBack(fmt.Sprintf("\t%s %s%s = %d;", dt, modelName, utils.Case2Camel(v.COLUMN_NAME), i))
		i++
	}
	for i := lst.Front(); i != nil; i = i.Next() {
		log.Println(i.Value)
	}
	return lst
}

/*
selectS employee.id,employee.name
*/
func (multiQuery *JoinQuery) FilterCs(tableTool *dbfactroy.DbFactroy) (cs *[]dbfactroy.COLUMNS) {

	cs = &[]dbfactroy.COLUMNS{}

	mapcs := multiQuery.findColumns(tableTool)
	for _, v := range strings.Split(multiQuery.Selects, ",") {
		v = strings.Trim(v, " ")
		log.Println(strings.Split(v, "."))
		c := multiQuery.checkExist(mapcs, v)
		if c == nil {
			panic(v + "不存在！")
		} else {
			*cs = append(*cs, *c)
		}
	}
	return
}

func (multiQuery *JoinQuery) iniFields(columns *[]dbfactroy.COLUMNS) (lst *list.List) {

	s := `
	entity.{{.colname}} = new({{.goType}})
	`

	lst = list.New()
	for _, v := range *columns {
		colname := utils.Case2Camel(v.TableName + "_" + v.COLUMN_NAME)
		goType := v.FindGoType(v.DATA_TYPE)

		vars := make(map[string]interface{})
		vars["goType"] = goType
		vars["colname"] = colname
		lst.PushBack(utils.ParseTemplateString(s, vars))

	}

	return lst
}

func (multiQuery *JoinQuery) buildGoFields(tableTool *dbfactroy.DbFactroy, columns *[]dbfactroy.COLUMNS) {

	lines := list.New()

	for _, v := range *columns {
		col_comment := fmt.Sprintf("\t/*  %s  */", v.COLUMN_COMMENT)
		lines.PushBack(col_comment)
		dt := tableTool.FindGoType(v.DATA_TYPE)

		col_def := v.TableName + "_" + v.COLUMN_NAME + ";type:" + v.COLUMN_TYPE
		if v.COLUMN_NAME == tableTool.Pkey {
			col_def = col_def + ";PRIMARY_KEY"
		}
		if tableTool.Dbtype_mysql {
			col_def = col_def + fmt.Sprintf(";comment:'%s'", v.COLUMN_COMMENT)
		}

		json_def := v.TableName + "_" + v.COLUMN_NAME
		if dt == "int64" {
			json_def = json_def + ",string"
		}
		fieldName := utils.Case2Camel(v.TableName + "_" + v.COLUMN_NAME)
		lines.PushBack(fmt.Sprintf("\t%s *%s `gorm:\"column:%s\" json:\"%s\"`",
			fieldName, dt, col_def, json_def))
	}

	for i := lines.Front(); i != nil; i = i.Next() {
		log.Println(i.Value)
	}
	multiQuery.GoFields = utils.List2string(lines)
}

func (multiQuery *JoinQuery) buildSelects() string {
	fields := []string{}
	for _, v := range strings.Split(multiQuery.Selects, ",") {
		v = strings.Trim(v, " ")
		fs := strings.Split(v, ".")
		v := v + " as " + strings.Join(fs, "_")
		fields = append(fields, v)

	}
	return strings.Join(fields, ",")

}

//timeStr := v.Birthday.Format("2006-01-02 15:04:05")
//   inst.MapJson2Field[] = ""   {{.MapJson2Field}}

func (multiQuery *JoinQuery) MapJson2Field(columns *[]dbfactroy.COLUMNS) (lst *list.List) {
	s := `
	inst.MapJson2Field["{{.JsonField}}"] = "{{.TableField}}"`

	lst = list.New()
	for _, v := range *columns {

		vars := make(map[string]interface{})
		vars["JsonField"] = v.TableName + "_" + v.COLUMN_NAME
		vars["TableField"] = v.TableName + "." + v.COLUMN_NAME

		map2fields := utils.ParseTemplateString(s, vars)
		lst.PushBack(map2fields)
	}

	return
}

func (multiQuery *JoinQuery) BuildIf(columns *[]dbfactroy.COLUMNS) (lst *list.List) {

	lst = list.New()
	for _, column := range *columns {
		stat := code.InstDaofactroy.FindStat(column)

		vars := make(map[string]interface{})
		vars["table_name"] = column.TableName
		vars["field_name"] = column.COLUMN_NAME
		vars["goFieldName"] = utils.Case2Camel(column.TableName + "_" + column.COLUMN_NAME)

		wheres := utils.ParseTemplateString(stat, vars)
		lst.PushBack(wheres)
	}

	return
}

func (multiQuery *JoinQuery) FindPgPkey(tableTool *dbfactroy.DbFactroy) {
	pkeys := tableTool.FindPgPkey(multiQuery.Tables[0])
	if len(pkeys) > 0 {
		tableTool.Pkey = pkeys[0].Colname
		tableTool.PkeyType = pkeys[0].Typename
	} else {
		tableTool.Pkey = "id"
		tableTool.PkeyType = "int64"
	}
}

func (multiQuery *JoinQuery) fbase() string {

	return "multiquery" + utils.Case2Camel(multiQuery.TableAlias)
}

func (multiQuery *JoinQuery) findColumns(tableTool *dbfactroy.DbFactroy) (mapcs map[string](*[]dbfactroy.COLUMNS)) {
	mapcs = make(map[string](*[]dbfactroy.COLUMNS))
	i := 1
	for _, table := range multiQuery.Tables {
		tableTool.Table = table
		mapcs[table] = tableTool.FindCOLUMNS()
		if i == 1 {
			multiQuery.Pkey = tableTool.Pkey
			multiQuery.PkeyType = tableTool.PkeyType
		}
		i++
	}
	if !tableTool.Dbtype_mysql {
		multiQuery.FindPgPkey(tableTool)
		multiQuery.Pkey = tableTool.Pkey
		multiQuery.PkeyType = tableTool.PkeyType
	}
	// fmt.Println(multiQuery)
	tableTool.Pkey = multiQuery.Pkey
	tableTool.PkeyType = multiQuery.PkeyType

	return
}

// 找各表字段定义
func (multiQuery *JoinQuery) buildGetSet(cs *dbfactroy.COLUMNS) string {
	s := `
	func (entity *{{.ModelPath}}MultiQryDTO) Get{{.FieldName}}() {{.goType}} {
		if entity.{{.FieldName}} == nil {
			{{.ReturnValue}}
		}
		return * entity.{{.FieldName}}		
		
	}

	func (entity *{{.ModelPath}}MultiQryDTO) Set{{.FieldName}}({{.FieldName}} {{.goType}}) {
		entity.{{.FieldName}}= &{{.FieldName}}
	}
`
	vars := make(map[string]interface{})

	vars["ModelName"] = utils.UcfirstCase2Camel(multiQuery.TableAlias)
	vars["FieldName"] = utils.Case2Camel(cs.TableName + "_" + cs.COLUMN_NAME)
	vars["goType"] = cs.FindGoType(cs.DATA_TYPE)

	vars["ReturnValue"] = cs.ReturnValue()

	return utils.ParseTemplateString(s, vars)
}

func (multiQuery *JoinQuery) buildGetSets(cs *[]dbfactroy.COLUMNS) string {
	var r []string
	for _, v := range *cs {
		r = append(r, multiQuery.buildGetSet(&v))
	}
	return strings.Join(r, "\n")

}
func (multiQuery *JoinQuery) buildProtoFile(tool *dbfactroy.DbFactroy) (dir, f, c string) {

	cs := multiQuery.FilterCs(tool)
	ps := multiQuery.MakeModelProtoBody(cs)

	fbase := multiQuery.fbase()
	dir = dbfactroy.InstFileFactroy.TransDir(dbfactroy.Gendir_proto, fbase)

	f, c = fbase+"Proto.proto", multiQuery.buildProto(tool, ps)

	dbfactroy.InstFileFactroy.WriteFileModule(dir, f, c)
	dbfactroy.InstFileFactroy.WriteFileModule(dbfactroy.Gendir_PBTool, f, c)
	multiQuery.AppendDfc(dir, f, c)

	return
}

// ReadTemplateServiceInterface
func (multiQuery *JoinQuery) buildProto(tableTool *dbfactroy.DbFactroy, ps *list.List) string {

	rb := dbfactroy.InstFileFactroy.ReadTemplateMqProto()

	vars := make(map[string]interface{})
	vars["Module"] = utils.Case2Camel(multiQuery.TableAlias) + multiPb
	vars["LcModule"] = strings.ToLower(utils.Case2Camel(multiQuery.TableAlias)) + multiPb
	vars["ModuleMessage"] = utils.List2stringBy(ps, "\n\t")
	vars["pkey"] = utils.Lcfirst(utils.Case2Camel(tableTool.Pkey))
	vars["pkeyType"] = tableTool.FindProtoType(tableTool.PkeyType)

	return utils.ParseTemplateString(string(rb), vars)

}

func (multiQuery *JoinQuery) buildDto(tableTool *dbfactroy.DbFactroy) string {

	bs := dbfactroy.InstFileFactroy.ReadTemplateMqDTO()
	cs := multiQuery.FilterCs(tableTool)

	multiQuery.buildGoFields(tableTool, cs)

	lines := multiQuery.iniFields(cs)

	vars := make(map[string]interface{})
	vars["ModelName"] = utils.Case2Camel(multiQuery.TableAlias)
	vars["buildGoFields"] = multiQuery.GoFields
	vars["iniFields"] = utils.List2string(lines)
	vars["GetSets"] = multiQuery.buildGetSets(cs)

	vars["FileName"] = "multiquery" + utils.Case2Camel(multiQuery.Table) + "Dto.go"
	vars["Description"] = "DTO" + utils.Case2Camel(multiQuery.Table) + "Dto"
	vars["Author"] = tableTool.Author
	vars["DATETIME"] = time.Now().Format(ichubbase.FormatDateTime)
	vars["ModelNames"] = strings.Join(multiQuery.Tables, ",")

	return utils.ParseTemplateString(string(bs), vars)

}

func (multiQuery *JoinQuery) buildDtoFile(tool *dbfactroy.DbFactroy) (dir string, f string, c string) {
	fbase := multiQuery.fbase()
	dir = dbfactroy.InstFileFactroy.TransDir(dbfactroy.Gendir_dto, fbase)

	f, c = fbase+"Dto.go", multiQuery.buildDto(tool)

	dbfactroy.InstFileFactroy.WriteFileModule(dir, f, c)
	multiQuery.AppendDfc(dir, f, c)

	return
}

func (multiQuery *JoinQuery) buildDao(tableTool *dbfactroy.DbFactroy) string {

	bs := dbfactroy.InstFileFactroy.ReadTemplateMqDAO()

	vars := make(map[string]interface{})
	vars["FileName"] = "multiquery" + utils.Case2Camel(multiQuery.Table) + "Dao.go"
	vars["Description"] = "DAO" + utils.Case2Camel(multiQuery.Table) + "Dao"
	vars["Author"] = tableTool.Author
	vars["DATETIME"] = time.Now().Format(ichubbase.FormatDateTime)

	vars["ModelName"] = utils.Case2Camel(multiQuery.TableAlias)

	vars["Table"] = multiQuery.Table
	vars["Selects"] = multiQuery.buildSelects()

	multiQuery.buildJoins(tableTool)
	vars["Joins"] = strings.Join(multiQuery.Joins, "\n\t")

	cs := multiQuery.FilterCs(tableTool)

	vars["BuildWheres"] = utils.List2string(multiQuery.BuildIf(cs))
	vars["MapJson2Field"] = utils.List2string(multiQuery.MapJson2Field(cs))

	return utils.ParseTemplateString(string(bs), vars)

}

func (multiQuery *JoinQuery) buildDaoFile(tool *dbfactroy.DbFactroy) (dir string, f string, c string) {

	fbase := multiQuery.fbase()
	dir = dbfactroy.InstFileFactroy.TransDir(dbfactroy.Gendir_dao, fbase)

	f, c = fbase+"Dao.go", multiQuery.buildDao(tool)
	dbfactroy.InstFileFactroy.WriteFileModule(dir, f, c)

	multiQuery.AppendDfc(dir, f, c)

	return
}

func (multiQuery *JoinQuery) buildService(tableTool *dbfactroy.DbFactroy, buildEs bool) string {

	bs := dbfactroy.InstFileFactroy.ReadTemplateMqService()

	vars := make(map[string]interface{})

	vars["FileName"] = "multiquery" + utils.Case2Camel(multiQuery.Table) + "Service.go"
	vars["Description"] = "服务multiquery" + utils.Case2Camel(multiQuery.Table) + "Service"
	vars["Author"] = tableTool.Author
	vars["DATETIME"] = time.Now().Format(ichubbase.FormatDateTime)
	vars["queryTable"] = multiQuery.Table

	vars["ModelName"] = utils.Case2Camel(multiQuery.TableAlias)
	vars["ESModelName"] = utils.Case2Camel(multiQuery.TableAlias)
	if buildEs {
		vars["ESModelName"] = "ES" + vars["ESModelName"].(string)
	}

	vars["pkeyField"] = tableTool.Pkey
	vars["pkey"] = utils.Case2Camel(tableTool.Pkey)
	vars["pkeyType"] = tableTool.FindProtoType(tableTool.PkeyType)
	vars["PkeyValue"] = "0"
	vars["fmtType"] = "%d"
	if tableTool.FindGoType(tableTool.PkeyType) == "string" {
		vars["PkeyValue"] = "\"\""
		vars["fmtType"] = "%s"
	}
	s := string(bs)
	if !buildEs {
		s = strings.Replace(s, "esservice", "service", 1)
	}
	return utils.ParseTemplateString(s, vars)

}

func (multiQuery *JoinQuery) BuildServiceFile(dbt *dbfactroy.DbFactroy) (dir, f, c string) {
	fbase := multiQuery.fbase()
	dir = dbfactroy.InstFileFactroy.TransDir(dbfactroy.Gendir_service, fbase)

	f, c = fbase+"Service.go", multiQuery.buildService(dbt, false)

	dbfactroy.InstFileFactroy.WriteFileModule(dir, f, c)
	multiQuery.AppendDfc(dir, f, c)

	return
}

func (multiQuery *JoinQuery) buildTestService(tableTool *dbfactroy.DbFactroy, buildEs bool) string {
	var bs interface{}
	if buildEs {
		bs = dbfactroy.InstFileFactroy.ReadTestTemplateMqESSrv()
	} else {
		bs = dbfactroy.InstFileFactroy.ReadTestTemplateMqSrv()
	}

	vars := make(map[string]interface{})

	vars["FileName"] = "Mq_" + utils.Case2Camel(multiQuery.Table) + "Srv_test.go"
	vars["Description"] = "测试服务Mq_" + utils.Case2Camel(multiQuery.Table) + "Srv"
	vars["Author"] = tableTool.Author
	vars["DATETIME"] = time.Now().Format(ichubbase.FormatDateTime)

	vars["queryTable"] = multiQuery.Table
	vars["ModelName"] = utils.Case2Camel(multiQuery.TableAlias)
	vars["ESModelName"] = utils.Case2Camel(multiQuery.TableAlias)
	if buildEs {
		vars["ESModelName"] = "ES" + vars["ESModelName"].(string)
	}
	vars["pkeyField"] = tableTool.Pkey
	vars["pkey"] = utils.Case2Camel(tableTool.Pkey)
	vars["pkeyType"] = tableTool.FindProtoType(tableTool.PkeyType)

	vars["StringFieldName"] = utils.Case2Camel(tableTool.StringFieldName)
	vars["StringFieldLen"] = tableTool.StringFieldLen

	vars["PkeyValue"] = "0"
	vars["fmtType"] = "%d"
	if tableTool.FindGoType(tableTool.PkeyType) == "string" {
		vars["PkeyValue"] = "\"\""
		vars["fmtType"] = "%s"
	} else if tableTool.FindGoType(tableTool.PkeyType) == "model.BitField" {
		vars["PkeyValue"] = "false"
		vars["fmtType"] = "%s"
	}

	s := string(bs.([]byte))
	if !buildEs {
		s = strings.ReplaceAll(s, "esservice", "service")
	}
	return utils.ParseTemplateString(s, vars)

}

func (multiQuery *JoinQuery) BuildTestServiceFile(dbt *dbfactroy.DbFactroy) (dir, f, c string) {

	fbase := "multiquery" + utils.Case2Camel(multiQuery.TableAlias)
	dir = dbfactroy.InstFileFactroy.TransDir(dbfactroy.Gendir_service_test, fbase)

	f, c = fbase+"Srv_test.go", multiQuery.buildTestService(dbt, false)

	dbfactroy.InstFileFactroy.WriteFileModule(dir, f, c)
	multiQuery.AppendDfc(dir, f, c)

	return dir, f, c
}

func (multiQuery *JoinQuery) Build(tool *dbfactroy.DbFactroy) (dir, f, c string) {

	fmt.Println("start build multiquery...")

	dir, f, c = multiQuery.buildDtoFile(tool)
	fmt.Println("finish build multiquery dto:" + dir + " " + f)
	fmt.Println("start build Proto...")

	dir, f, c = multiQuery.buildProtoFile(tool)

	fmt.Println("finish build multiquery Proto:" + dir + " " + f)

	dir, f, c = multiQuery.buildDaoFile(tool)
	fmt.Println("finish build multiquery dao:" + dir + " " + f)

	dir, f, c = multiQuery.BuildServiceFile(tool)
	fmt.Println("finish build multiquery service: " + dir + " " + f + "!")

	dir, f, c = multiQuery.BuildTestServiceFile(tool)
	fmt.Println("finish build testServiceFile: " + dir + " " + f + "!")

	multiQuery.LogDfc()
	multiQuery.SendEmail()

	return
}

func (multiQuery *JoinQuery) Demo(tool *dbfactroy.DbFactroy) {

	multiQuery.Ini(tool, "cms_column,cms_content")
	multiQuery.SelectFields("cms_column.column_id,cms_content.id")
	multiQuery.Keys["cms_column.column_id"] = "cms_content.id"

	multiQuery.Build(tool)

}
